/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Library General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor Boston, MA 02110-1301,  USA
 */
 
/**
 * @version bridge.c 0.0.1
 * @package Comoro
 * @author Dr.NP <np@bsgroup.org>
 * 
 * Base server to center connector
 */

#include "bspd.h"

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <fcntl.h>
#include <time.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <event.h>
#include <pthread.h>

#ifdef HAVE_MALLOC_H
#ifndef __OpenBSD__
#include <malloc.h>
#endif
#endif

extern struct conf setting;
extern struct event_base *ev_base;
static int bridge_mode = BRIDGE_MODE_LOGIN;
struct conn_connector *c = (struct conn_connector *) NULL, *l = (struct conn_connector *) NULL;
static pthread_mutex_t bridge_lock;

/* Initializer */
int bridge_init(int mode)
{
	fprintf(stderr, "Bridge initialized as mode %d\n", mode);
	bridge_mode = mode;
	
	if (mode == BRIDGE_MODE_BASE)
	{
		// Init center connection
		c = (struct conn_connector *) malloc(sizeof(struct conn_connector));

		memset(c, 0, sizeof(struct conn_connector));

		c->srv_addr = inet_addr(setting.center_srv_addr);
		c->srv_port = setting.center_srv_port;
		c->nonblock = 1;
		c->tcp_send_buffer_size = 131072;
		c->tcp_recv_buffer_size = 131072;
		c->tcp_nodelay = 1;
		c->on_data_callback = bridge_on_read;
		c->on_close_callback = bridge_on_close;

		// A new connector to center server
		conn_tcp_connector_new(c);
	}
	
	if (mode == BRIDGE_MODE_BASE || mode == BRIDGE_MODE_UNITED)
	{
		// Init login connection
		l = (struct conn_connector *) malloc(sizeof(struct conn_connector));

		memset(l, 0, sizeof(struct conn_connector));

		l->srv_addr = inet_addr(setting.login_srv_addr);
		l->srv_port = setting.login_srv_status_port;
		l->nonblock = 1;
		l->tcp_send_buffer_size = 131072;
		l->tcp_recv_buffer_size = 131072;
		l->tcp_nodelay = 1;
		l->on_data_callback = bridge_on_login_read;
		l->on_conn_callback = base_reg_login;
		l->on_close_callback = bridge_on_login_close;
		
		// A new connector to login server
		conn_tcp_connector_new(l);
	}
	
	return RTN_SUCCESS;
}

/* Get bridge mode */
int bridge_get_mode()
{
	return bridge_mode;
}

/* Register client into center */
int bridge_reg_center(int client_id, unsigned long long user_id, char *username, unsigned int runner_id)
{
	static char edata[20 + MAX_USERNAME_LENGTH];

	pthread_mutex_lock(&bridge_lock);
	
	put_u_int((unsigned int) SGN_CLIENT_REG, edata);
	put_u_int((unsigned int) client_id, edata + 4);
	put_u_ll(user_id, edata + 8);
	put_u_int(runner_id, edata + 16);
	memcpy(edata + 20, username, MAX_USERNAME_LENGTH);

	if (bridge_get_mode() == BRIDGE_MODE_UNITED)
	{
		// Transfer internal
		center_on_read(-1, (void *) edata, 20 + MAX_USERNAME_LENGTH);
	}

	else
	{
		// Socket
		if (!c)
		{
			return RTN_GENERAL;
		}

		conn_tcp_connector_send(c, (const char *) edata, 20 + MAX_USERNAME_LENGTH);
	}

	pthread_mutex_unlock(&bridge_lock);

	return RTN_SUCCESS;
}

/* Unregister client from center */
int bridge_unreg_center(int client_id)
{
	char edata[8];
	
	put_u_int((unsigned int) SGN_CLIENT_UNREG, edata);
	put_u_int((unsigned int) client_id, edata + 4);

	if (bridge_get_mode() == BRIDGE_MODE_UNITED)
	{
		// Transfer internal
		center_on_read(-1, (void *) edata, 8);
	}

	else
	{
		// Socket
		if (!c)
		{
			return RTN_GENERAL;
		}

		conn_tcp_connector_send(c, (const char *) edata, 8);
	}

	return RTN_SUCCESS;
}

/* Send data to center server */
int bridge_to_center(int client_id, char *data, int len)
{
	int r = len;
	char *edata;

	if ((unsigned int) client_id > SGN_RANGE)
	{
		// Special command
		edata = (char *) malloc(len + 4);
		memcpy(edata + 4, data, len);
		put_u_int((unsigned int) client_id, edata);
		r = len + 4;
	}

	else
	{
		edata= package_decode(data, len, 8);
		put_u_int((unsigned int) len + 8, edata);
		put_u_int((unsigned int) client_id, edata + 4);
		r = len + 8;
	}

	if (bridge_get_mode() == BRIDGE_MODE_UNITED)
	{
		// Transfer internal
		center_on_read(-1, (void *) edata, r);
	}

	else
	{
		// Socket
		if (!c)
		{
			return RTN_GENERAL;
		}

		r = conn_tcp_connector_send(c, (const char *)edata, r);
	}

	if (edata)
	{
		free(edata);
	}
	
	return r;
}

/* Send data to base server */
int bridge_to_base(int base_id, char *data, int len)
{
	int r = len;
	char *edata = (char *) malloc(len + 4);

	put_u_int((unsigned int) len + 4, edata);
	memcpy(edata + 4, data, len);
	
	if (bridge_get_mode() == BRIDGE_MODE_UNITED)
	{
		// Transfer internal
		bridge_on_read((void *) edata, len + 4);
	}

	else
	{
		// Socket
		r = conn_client_send(conn_client_list_get(base_id), (const char *) edata, len + 4);
	}

	if (edata)
	{
		free(edata);
	}

	return r;
}

/* Send data to login server */
int bridge_to_login(char *data, int len)
{
	return conn_tcp_connector_send(l, (const char *)data, len);
}

/* Send data to base client */
int bridge_to_client(int global_id, char *data, int len)
{
	struct global_client_item *gi = center_get_base_info(global_id);

	if (!gi)
	{
		return RTN_GENERAL;
	}
	
	int r = len;
	char *edata = package_encode(data, len, 16);

	// Transfer length
	put_u_int((unsigned int) len + 16, edata);
	// Client id
	put_u_int((unsigned int) gi->client_id, edata + 4);
	// Package length
	put_u_int((unsigned int) len + 8, edata + 8);
	// Blank for sequence id
	put_u_int(0, edata + 12);
	
	if (bridge_get_mode() == BRIDGE_MODE_UNITED)
	{
		// Transfer internal
		bridge_on_read((void *) edata, len + 16);
	}

	else
	{
		// Socket
		r = conn_client_send(conn_client_list_get(gi->base_id), (const char *) edata, len + 16);
	}

	if (edata)
	{
		free(edata);
	}
	
	return r;
}

/* On data received from center */
void bridge_on_read(void *data, unsigned int len)
{
	unsigned int data_remain = len;
	unsigned int plen;
	unsigned int spa;

	while (data_remain > 0)
	{
		if (data_remain >= 4)
		{
			plen = get_u_int(data + len - data_remain);

			if (plen > SGN_RANGE && data_remain >= 9)
			{
				spa = get_u_int(data + len - data_remain + 4);
				base_from_center((int) plen, data + len - data_remain, 8);

				data_remain -= 8;
			}

			else if (4 <= plen && data_remain >= plen)
			{
				spa = get_u_int(data + len - data_remain + 4);
				base_from_center((int) spa, data + len - data_remain + 8, plen - 8);

				data_remain -= plen;
			}

			else
			{
				// Half package
				data_remain = 0;
			}
		}

		else
		{
			break;
		}
	}
	
	return;
}

/* On data received from login */
void bridge_on_login_read(void *data, unsigned int len)
{
	unsigned int data_remain = len;
	unsigned int plen;
	static char signal[12];

	//package_output(data, len);
	while (data_remain > 0)
	{
		if (data_remain >= 4)
		{
			plen = get_u_int(data + len - data_remain);

			if (plen > SGN_RANGE)
			{
				// Session here
				if (plen == SGN_SESSION_DATA && data_remain >= 16 + MAX_USERNAME_LENGTH + SESSION_KEY_LENGTH)
				{
					unsigned long long user_id = get_u_ll(data + len - data_remain + 4);
					int app_id = (int) get_u_int(data + len - data_remain + 12 + MAX_USERNAME_LENGTH + SESSION_KEY_LENGTH);
					base_on_session(user_id, data + len - data_remain + 12, data + len - data_remain + MAX_USERNAME_LENGTH + 12, app_id);

					// Send response
					put_u_int(SGN_SESSION_DATA, signal);
					put_u_ll(user_id, signal + 4);
					conn_tcp_connector_send(l, signal, 12);
					
					data_remain -= 16 + MAX_USERNAME_LENGTH + SESSION_KEY_LENGTH;

					continue;
				}

				else if (plen == SGN_HEARTBEAT)
				{
					data_remain -= 8;

					continue;
				}

				else
				{
					break;
				}
			}

			else
			{
				// What?
				data_remain -= 4;
			}
		}

		else
		{
			break;
		}
	}

	return;
}

/* On disconnected from login */
void bridge_on_login_close()
{
	fprintf(stderr, "Disconnected from login server\n");
	log_add("Disconnected from login server");

	return;
}

/* On bridge closed */
void bridge_on_close()
{
	fprintf(stderr, "Bridge closed\n");
	
	return;
}
